曾經以為[each == 迭代(Iteration)
, map == 枚舉(enumerate)
],後來發現錯得非常離譜。
有請:
教育部重編國語辭典修訂本
交換替代。《文選.張衡.東京賦》:「於是春秋改節,四時迭代。」北周.庾信〈哀江南賦序〉:「嗚呼!山岳崩頹,既履危亡之運,春秋迭代,必有去故之悲,天意人事,可以悽愴傷心者矣!」
一個接替一個,做重複的事。與迴圈感覺上很像?
再有請:
自由的百科全書維基百科
迴圈是計算機科學運算領域的用語,也是一種常見的控制流程。迴圈是一段在程式中只出現一次,但可能會連續執行多次的程式碼。迴圈中的程式碼會執行特定的次數,或者是執行到特定條件成立時結束迴圈,或者是針對某一集合中的所有項目都執行一次。
迭代是指一個接著一個交換做,迴圈是指重複執行。
會用到each與map的都是"集合資料"類別,例如Array與Hash。
2.7.3 :008 > [1, 2, 3].each
=> #<Enumerator: [1, 2, 3]:each>
2.7.3 :009 > {:a => 1, :b => 2, :c => 2}.each
=> #<Enumerator: {:a=>1, :b=>2, :c=>2}:each>
2.7.3 :010 > (1..5).each
=> #<Enumerator: 1..5:each>
2.7.3 :011 > 5.times.each
=> #<Enumerator: 5:times>
2.7.3 :012 > [1, 2, 3].map
=> #<Enumerator: [1, 2, 3]:map>
#map大同小異。
#Enumerator枚舉器。
配合昨天說的block。
2.7.3 :013 > [1, 2, 3].each {|num| puts num + 1}
2
3
4
=> [1, 2, 3]
2.7.3 :014 > [1, 2, 3].map {|num| num + 1}
=> [2, 3, 4]
#一個一個元素進去操作,這件事情就是迭代。
可以看到有說明each、map為迭代器的資料,但它們是不是枚舉器?
教育部重編國語辭典修訂本。
一一列舉。如:「隨著科技進步,全世界每日新發明的產品,真是不勝枚舉。」金.元好問〈故物譜〉:「住在鄉里,常侍諸父及兩兄燕談,每及家所有書,則必枚舉而問之。」元.陶宗儀《南村輟耕錄.卷一九.闌駕上書》:「歌曰:『官吏黑漆皮燈籠,奉使來時添一重。』如此怨謠,未能枚舉,皆萬姓不平之氣,鬱結于懷,而發諸聲者然也。」
在block內做的事情,就是一一列舉。
2.7.3 :008 > [1, 2, 3].each
=> #<Enumerator: [1, 2, 3]:each>
2.7.3 :013 > [1, 2, 3].each {|num| puts num + 1}
2
3
4
=> [1, 2, 3]
When a block given, passes each successive array element to the block。-- form Array
Calls the given block with each key-value pair。-- form Hash
集合資料類別使用each與map,會產生枚舉器Enumerator:
,接上black後會有迭代的行為,送進block後進行枚舉
2.7.3 :001 > Enumerator.is_a? Class
=> true
2.7.3 :012 > new_enume = Enumerator.new {}
=> #<Enumerator: #<Enumerator::Generator:0x00007ff6ad33e900>:each>
是的,有這個類別,在一開始我是真的嚇到,枚舉也包裝成類別了,但後來越了解Ruby裏面都是物件這件事後,就覺得大驚小怪了,難怪枚舉這麼強大。
這是Ruby API示範的費波......放棄,Fibonacci係數。
fib = Enumerator.new do |y|
a = b = 1
loop do
y << a
a, b = b, a + b
end
end
fib.take(10)
說明一下
"<<",感覺上跟很眼熟。
2.7.3 :009 > [] << 2
=> [2]
2.7.3 :010 > [] << [2]
=> [[2]]
還記得前面說過,Ruby有很多相同名稱方法,在Enumerator類別中,變數使用<<
成為yielder
的對象來取得值,可以當成方法的別名,而其實在Array中,<<
是push
方法的別名。但行為上都很像賦予值對吧,更感覺到duck typing
的好處了。
會有別名方法的簡單介紹的....如果時間夠。
冷知識,map是collect的別名,為了跳槽的魔法師們設計,當然是語法裡有map的魔法師們。
繼續回到官網示範。
fib = Enumerator.new do |y|
a = b = 1
loop do
y << a # 循環中y被賦予a值,每個變數被賦予a的值。
a, b = b, a + b # 但a與b的值,會依造此規則變化。
end
end
2.7.3 :010 > fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
還可以這樣玩。
2.7.3 :042 > fib = Enumerator.new do |y|
2.7.3 :043 > i = 0
2.7.3 :044 > a, b = 2, 3
2.7.3 :045 > loop do
2.7.3 :046 > y << a
2.7.3 :047 > a, b = b, a+b
2.7.3 :048 > i += 1
2.7.3 :049 > puts "某題:#{i}年後,動物有#{a}隻的問題"
2.7.3 :050 > end
2.7.3 :051 > end
=> #<Enumerator: #<Enumerator::Generator:0x00007f9b6e469fb8>:each>
2.7.3 :052 > fib.take(10)
某題:1年後,動物有3隻的問題
某題:2年後,動物有5隻的問題
某題:3年後,動物有8隻的問題
某題:4年後,動物有13隻的問題
某題:5年後,動物有21隻的問題
某題:6年後,動物有34隻的問題
某題:7年後,動物有55隻的問題
某題:8年後,動物有89隻的問題
某題:9年後,動物有144隻的問題
=> [2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
雖然明明也是跟定義方法很像,但怎麼感覺這樣處理,比較帥?
Enumerator
類別博大精深,它還有子系列是Enumerator::
,有興趣可以先看看Enumerator::Lazy
,簡單一點的說法是lazy
這個子系列避免枚舉元素過多,屬於優化的一種,Rails
內也會有所謂的Lazy load
喔。高階魔法....
Ruby API https://ruby-doc.org/core-3.0.2/Enumerable.html
Public Instance Methods
手冊上可以看到有被標注Enumerable
的方法們,說是Ruby的精髓可能太誇張,但可以說是處理各種資料的專屬Ruby利器,例如昨日提到的sort_by
。
以下是騙篇幅用的整理資料,不討論用法,可以自行google翻譯看看,看到中文看會不會順利想到正確用法,可以更發現Ruby有多口語化。
`all?`、`any?`、`chain`、`chunk`、`chunk_while`、`collect`、`collect_concat`、`count`、`cycle`、`deect`、`drop`、`drop_while`、`each_cons`、`each_entry`、`each_slice`、`each_with_index`、`each_with_object`、`entries`、`filter`、`filter_map`、`find`、`find_all`、`find_index`、`first`、 `flat_map`、`grep`、`grep_v`、`group_by`、`include?`、`inject`、`lazy`、`map`、`max`、`max_by`、`member?`、`min`、`min_by`、`minmax`、`minmax_by`、`none?`、`one?`、`partition`、`reduce`、`reject`、`reverse_each`、`select`、`slice_after`、`slice_before`、`slice_when`、`sort`、`sort_by`、`sum`、`take`、`take_while`、`tally`、`to_a`、`to_h`、`uniq`、`zip`
用時沒發現,回頭整理才發現,原來用過的很多方法,都是有枚舉這個行為存在。
2.7.3 :054 > ruby_chain = [1, 2].chain(3..9)
=> #<Enumerator::Chain: [[1, 2], 3..9]>
2.7.3 :056 > ruby_chain.to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
當初還以為這方法只是簡單連接而已,結果也可以枚舉。
2.7.3 :090 > arr = %w(惡魔靈魂 黑暗靈魂 黑暗靈魂2 血源詛咒 黑暗靈魂3)
=> ["惡魔靈魂", "黑暗靈魂", "黑暗靈魂2", "血源詛咒", "黑暗靈魂3"]
2.7.3 :091 > list = arr.to_enum
=> #<Enumerator: ["惡魔靈魂", "黑暗靈魂", "黑暗靈魂2", "血源詛咒", "黑暗靈魂3"]:each>
2.7.3 :092 > list.next
=> "惡魔靈魂"
2.7.3 :093 > list.next
=> "黑暗靈魂"
2.7.3 :094 > list.next
=> "黑暗靈魂2"
2.7.3 :095 > list.next
=> "血源詛咒"
2.7.3 :096 > list.next
=> "黑暗靈魂3"
2.7.3 :097 > list.next
StopIteration (iteration reached an end)
可以利用next
方法進行分次枚舉,也是利用到昨天的yield
原理。
一樣看程式碼比較有感覺。
2.7.3 :098 > games = Object.new
=> #<Object:0x00007f9b6e3f07a8>
2.7.3 :099 > def games.each
2.7.3 :100 > p "惡魔靈魂先出在PS3"
2.7.3 :101 > yield
2.7.3 :102 > p "黑暗靈魂也出在PS3"
2.7.3 :103 > yield
2.7.3 :104 > p "黑暗靈魂2也先出在PS3"
2.7.3 :105 > yield
2.7.3 :106 > p "血源詛咒出在PS4"
2.7.3 :107 > yield
2.7.3 :108 > p "黑暗靈魂3出在PS4"
2.7.3 :109 > yield
2.7.3 :110 > p "下一款叫做Elden Ring"
2.7.3 :111 > end
=> :each
2.7.3 :112 > test = games.to_enum
=> #<Enumerator: #<Object:0x00007f9b6e3f07a8>:each>
2.7.3 :113 > test.next
"惡魔靈魂先出在PS3"
=> nil
2.7.3 :114 > test.next
"黑暗靈魂也出在PS3"
=> nil
2.7.3 :115 > test.next
"黑暗靈魂2也先出在PS3"
=> nil
2.7.3 :116 > test.next
"血源詛咒出在PS4"
=> nil
2.7.3 :117 > test.next
"黑暗靈魂3出在PS4"
=> nil
2.7.3 :118 > test.next
"下一款叫做Elden Ring"
StopIteration (iteration reached an end)
看來是很想玩game了
第五天的Leetcode350:Intersection of Two Arrays II
但是先看一下它的前一題Leetcode349:Intersection of Two Arrays
349的題目連結:https://leetcode.com/problems/intersection-of-two-arrays/
題目重點:取兩個陣列交集的元素,必須uniq。
# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def intersection(nums1, nums2)
end
有時間可以研究迴圈怎麼跑,可以幫助思考怎麼枚舉,沒時間就用Ruby內建方法。
2.7.3 :001 > [2, 5, 6, 3, 8] & [1, 2, 4, 7 ,4]
=> [2]
所以答案當然是
def intersection(nums1, nums2)
nums1 & nums2
end
可以於Array類別內看到&
這方法。
&
的原理。
nums1.uniq - ( nums1.uniq - nums2.uniq )
2.7.3 :122 > [1, 2, 4, 6, 7].intersection([4, 6])
=> [4, 6]
2.7.3 :123 > [1, 2, 4, 6, 7].intersection([4, 6], [1,7])
=> []
2.7.3 :124 > [1, 2, 4, 6, 7].intersection([1, 4, 6], [1, 4, 7])
=> [1, 4]
350的題目連結:https://leetcode.com/problems/intersection-of-two-arrays-ii/
題目重點:交集數不再是uniq,例如都有兩個2,就是要出現2次。
# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def intersect(nums1, nums2)
end
邏輯大概如下
$ #指針又出來了
[4, 9, 5] #arr1
[9, 4, 9, 8, 4] #arr2
{} #準備一個記錄器
#開跑
$ #下面的陣列,我有一個4,你有沒有4?
[4, 9, 5]
[9, 4, 9, 8, 4].count(4) #=>2 有,2個。
{4 => {arr1: 1, arr2:2}} #紀錄下來
#繼續
$ #下面的陣列,我一個9,你?
[4, 9, 5]
[9, 4, 9, 8, 4].count(9) #=>2 有,2個
{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}} #紀錄下來
#繼續
$ #下面的陣列,我一個5,你?
[4, 9, 5]
[9, 4, 9, 8, 4].count(5) #=>0 ,沒有。
{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}} #沒有就不必紀錄。
#不用繼續了,跑完了,即使arr2有不同元素,也成為不了交集。
#分析將
{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}}
#成為一個陣列
4是交集,arr2出現2次,arr1出現一次,依照規則是取小的。
所以像是
2.7.3 :125 > x = [2 , 1].min
=> 1
2.7.3 :129 > [].push(4)*x
=> [4]
#9以此類推。
349讓我們暸解交集這件事,那我們就省去了檢測這件事了。
def intersect(nums1, nums2)
arr = []
(nums1 & nums2).each do |num|
arr += Array.new([nums1.count(num), nums2.count(num)].min , num)
end
arr
end
#原理
2.7.3 :130 > [2, 1].min
=> 1
2.7.3 :131 > Array.new(5, 1)
=> [1, 1, 1, 1, 1]
#提醒下面寫法不行
2.7.3 :144 > [] += Array.new(4) #噴錯
2.7.3 :142 > arr = []
=> []
2.7.3 :143 > arr += arr << 4
=> [4, 4] #資料不正確。
記得回傳arr
用map也可以,一樣還是要回傳arr,所以我用each。
今日有提到的
1.迭代、 枚舉
中間提到的動物問題
真的是考古題喔!
2.Leetcode350:Intersection of Two Arrays II
明日利用&
做一個很陽春的名字檢驗器
,以及分享幾題考古題。